/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.aop.asm;

import cn.easyplatform.aop.AopCallback;
import cn.easyplatform.org.objectweb.asm.ClassWriter;
import cn.easyplatform.org.objectweb.asm.MethodVisitor;
import cn.easyplatform.org.objectweb.asm.Opcodes;
import cn.easyplatform.org.objectweb.asm.Type;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * 
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a>
 * 
 */
class ClassY implements Opcodes {

	ClassWriter cw;

	String myName;

	String enhancedSuperName;

	Method[] methodArray;

	Constructor<?>[] constructors;

	private Class<?> superClass;

	ClassY(Class<?> klass, String myName, Method[] methodArray,
			Constructor<?>[] constructors) {
		this.myName = myName.replace('.', '/');
		this.enhancedSuperName = klass.getName().replace('.', '/');
		this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		cw.visit(AsmClassAgent.CLASS_LEVEL, ACC_PUBLIC, this.myName, null,
				enhancedSuperName, getParentInterfaces(klass));
		this.methodArray = methodArray;
		this.constructors = constructors;
		this.superClass = klass;
	}

	String[] getParentInterfaces(Class<?> xClass) {
		Class<?>[] its = xClass.getInterfaces();
		if (its == null || its.length == 0)
			return new String[] { AopCallback.class.getName().replace('.', '/') };
		else {
			String[] iii = new String[its.length + 1];
			for (int i = 0; i < its.length; i++)
				iii[i] = its[i].getName().replace('.', '/');
			iii[its.length] = AopCallback.class.getName().replace('.', '/');
			return iii;
		}
	}

	String[] convertExp(Class<?>[] expClasses) {
		if (expClasses.length == 0)
			return null;
		String[] results = new String[expClasses.length];
		for (int i = 0; i < results.length; i++)
			results[i] = expClasses[i].getName().replace('.', '/');
		return results;
	}

	int getAccess(int modify) {
		if (Modifier.isProtected(modify))
			return ACC_PROTECTED;
		if (Modifier.isPublic(modify))
			return ACC_PUBLIC;
		return 0x00;
	}

	static int findMethodIndex(String name, String desc, Method[] methods) {
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if (Type.getMethodDescriptor(method).equals(desc)
					&& method.getName().equals(name))
				return i;
		}
		return -1;// 是否应该抛出异常呢?应该不可能发生的
	}

	void addConstructors() {
		for (Constructor<?> constructor : constructors) {
			String[] expClasses = convertExp(constructor.getExceptionTypes());
			String desc = Type.getConstructorDescriptor(constructor);
			int access = getAccess(constructor.getModifiers());
			MethodVisitor mv = cw.visitMethod(access, "<init>", desc, null,
					expClasses);
			new ChangeToChildConstructorMethodAdapter(mv, desc, access,
					enhancedSuperName).visitCode();
		}
	}

	byte[] toByteArray() {
		addField();
		addConstructors();
		addAopMethods();
		enhandMethod();
		cw.visitSource(superClass.getSimpleName() + ".java", null);
		return cw.toByteArray();
	}

	private void enhandMethod() {
		for (Method method : methodArray) {
			String methodName = method.getName();
			String methodDesc = Type.getMethodDescriptor(method);
			int methodAccess = getAccess(method.getModifiers());
			MethodVisitor mv = cw.visitMethod(methodAccess, methodName,
					methodDesc, null, convertExp(method.getExceptionTypes()));
			int methodIndex = findMethodIndex(methodName, methodDesc,
					methodArray);
			AopMethodAdapter adapter = new AopMethodAdapter(mv, methodAccess,
					methodName, methodDesc, methodIndex, myName,
					enhancedSuperName);
			adapter.visitCode();
			adapter.visitAttribute();
		}
	}

	private void addAopMethods() {
		new AopInvokeAdpter(methodArray, cw.visitMethod(ACC_PUBLIC,
				"_aop_invoke", "(I[Ljava/lang/Object;)Ljava/lang/Object;",
				null, null), ACC_PUBLIC, "invoke",
				"(I[Ljava/lang/Object;)Ljava/lang/Object;", 0, myName,
				enhancedSuperName).visitCode();
	}

	private void addField() {
		cw.visitField(ACC_PRIVATE + ACC_STATIC,
				AsmClassAgent.MethodArray_FieldName,
				"[Ljava/lang/reflect/Method;", null, null).visitEnd();
		cw.visitField(ACC_PRIVATE + ACC_STATIC,
				AsmClassAgent.MethodInterceptorList_FieldName,
				"[Ljava/util/List;",
				"[Ljava/util/List<Lcn/easyplatform/aop/MethodInterceptor;>;",
				null).visitEnd();
	}

	static <T> byte[] enhandClass(Class<T> kclass, String myName,
			Method[] methodArray, Constructor<?>[] constructors) {
		return new ClassY(kclass, myName, methodArray, constructors)
				.toByteArray();
	}
}
